/**
 * 
 */
package com.yangtao.prototype.system.dao;

import java.sql.SQLException;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.stereotype.Repository;

import com.yangtao.framework.hibernate.BaseDao;
import com.yangtao.framework.util.DateHelper;
import com.yangtao.framework.util.StringHelper;
import com.yangtao.prototype.system.entity.SerialNumber;
import com.yangtao.prototype.system.entity.SnSetup;

/**
 * @author 流水号
 * 
 */
@Repository
public class SerialNumberDAO extends BaseDao<SerialNumber> {
	@Autowired
	private SnSetupDAO settingDao;
	// 静态的号码池
	private static ConcurrentHashMap<String, NumPool> numberPool = new ConcurrentHashMap<String, NumPool>();

	public SerialNumberDAO() {
	}

	/**
	 * 根据单据类型 取到下一个流水号
	 * 
	 * @param docType
	 *            单价类型
	 * @return 下一个流水号
	 */
	public String getNextNumber(String docType) {
		if (numberPool.get(docType) == null) {
			// System.out.println(Thread.currentThread().getName()
			// + "池子里面没有对象");
			initPool(docType);
		}
		return numberPool.get(docType).getNext();
	}

	/**
	 * 初始化所有在流水号设置表中的流水号类型的号码池
	 */
	public void initPool() {
		List<SnSetup> settings = settingDao.findAll();
		for (SnSetup setting : settings) {
			NumPool pool = new NumPool(setting.getNoteType());
			numberPool.put(setting.getNoteType(), pool);
		}
	}

	/**
	 * 初始化指定类型的流水号的号码池
	 * 
	 * @param docType
	 *            流水号类型
	 */
	public void initPool(String docType) {
		NumPool pool = new NumPool(docType);
		numberPool.put(docType, pool);
	}

	/**
	 * 流水号向下滚动一次
	 * 
	 * @param noteType
	 *            单据类型
	 * @param dateStr
	 *            日期字符串
	 * @param poolsize
	 *            滚动长度
	 * @return 滚动起始的号码
	 */
	public Integer rolling(final String noteType, final String dateStr,
			final int rollingSize) {
		return getHibernateTemplate().executeWithNewSession(
				new HibernateCallback<Integer>() {
					@Override
					public Integer doInHibernate(Session session)
							throws HibernateException, SQLException {
						Transaction trans = session.beginTransaction();
						String hql = "from SerialNumber sn where sn.noteType='"
								+ noteType + "' and dateStr='" + dateStr + "'";
						Query query = session.createQuery(hql);
						SerialNumber sn = (SerialNumber) query.uniqueResult();
						Integer beginNum = 1;
						if (sn == null) {
							sn = new SerialNumber();
							sn.setDateStr(dateStr);
							sn.setNoteType(noteType);
							sn.setRollingNum(rollingSize + beginNum);
							session.save(sn);
						} else {
							beginNum = sn.getRollingNum();
							sn.setRollingNum(beginNum + rollingSize);
							session.update(sn);
						}
						trans.commit();
						session.close();
						return beginNum;
					}
				});
	}

	/**
	 * 号码池的类 里面有一个池的长度和对应长度的字符串数组 根据当前的指针位置，返回对应的号码段
	 * 
	 * @author 杨涛
	 */
	public class NumPool {
		/**
		 * 单据类型
		 */
		private String noteType;
		/**
		 * 号码池的长度
		 */
		private int poolSize = 50;
		/**
		 * 单据的数组
		 */
		private String[] numbers;
		/**
		 * 当前指针位置
		 */
		private int pointer;

		/**
		 * 初始化号码池
		 * 
		 * @param noteType
		 *            单据类型
		 */
		public NumPool(String noteType) {
			this.noteType = noteType;
			initlize();
		}

		/**
		 * 初始化号码池
		 * 
		 * @param noteType
		 *            单据类型
		 * @param size
		 *            号码池长度
		 */
		public NumPool(String noteType, int size) {
			this.noteType = noteType;
			this.poolSize = size;
			initlize();
		}

		/**
		 * 初始化号码池
		 */
		private void initlize() {
			pointer = 0;
			numbers = new String[poolSize];
			// 流水号默认设置
			String dateFmt = "yyMMdd";
			String notePrefix = "";
			String leftPadChar = "0";
			int growStep = 1;
			int snLength = 6;
			// 取得单据设置信息
			String sql = "from SnSetting ss where ss.noteType ='" + noteType
					+ "'";
			SnSetup setting = settingDao.findUnique(sql);
			// 如果流水号格式已经设定，采取设定值，否则取默认值
			if (setting != null) {
				dateFmt = setting.getDateFmt();
				notePrefix = setting.getNotePrefix();
				leftPadChar = setting.getLeftPadChar();
				growStep = setting.getGrowStep() == null ? growStep : setting
						.getGrowStep();
				snLength = setting.getSnLength() == null ? snLength : setting
						.getSnLength();
			}
			String dateStr = DateHelper.format(new Date(), dateFmt);
			// 取得号码池初始化号码
			int beginNumber = rolling(noteType, dateStr, poolSize * growStep);
			// 初始化号码池
			for (int i = 0; i < poolSize; i++) {
				numbers[i] = notePrefix
						+ dateStr
						+ StringHelper.leftPad(String.valueOf(beginNumber),
								snLength, leftPadChar);
				beginNumber = beginNumber + growStep;
			}
		}

		/**
		 * 取到下一个单号 <br>
		 * 这是一个同步的方法，保证了取得号码时不重复
		 * 
		 * @return
		 */
		public synchronized String getNext() {
			// synchronized (this) {
			// System.out.println(Thread.currentThread().getName() + "当前指针 "
			// + pointer);
			if (pointer < poolSize) {
				return synchronizeFlowNumber();
			} else {
				// 清空号码池
				clear();
				// 重新初始化号码池
				initlize();
				return synchronizeFlowNumber();
			}
		}

		/**
		 * 返回下一个单号
		 * @return
		 */
		private String synchronizeFlowNumber() {
			String nextNumber = numbers[pointer];
			pointer++;
			return nextNumber;
		}

		/**
		 * 清空号码池
		 */
		private void clear() {
			numbers = null;
			pointer = 0;
		}
	}
}
